/**
 * Copyright © 2008, EMC Corporation.
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 *
 *     + Redistributions of source code must retain the above copyright notice, 
 *       this list of conditions and the following disclaimer.
 *     + Redistributions in binary form must reproduce the above copyright 
 *       notice, this list of conditions and the following disclaimer in the 
 *       documentation and/or other materials provided with the distribution.
 *     + The name of EMC Corporation may not be used to endorse or promote 
 *       products derived from this software without specific prior written 
 *       permission.
 *
 *      THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 *      "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 
 *      TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
 *      PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 
 *      BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
 *      CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 *      SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 *      INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 *      CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 *      ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 *      POSSIBILITY OF SUCH DAMAGE.
 */
package com.emc.esu.api.test {
	
	import com.emc.esu.api.*;
	import com.emc.esu.api.rest.EsuRestApi;
	import com.emc.esu.api.rest.EsuRestApiHttp;
	import com.emc.esu.api.rest.RestLoader;
	import com.emc.esu.helpers.*;
	import com.hurlant.crypto.prng.Random;
	
	import flash.events.ErrorEvent;
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.net.URLLoader;
	import flash.net.URLLoaderDataFormat;
	import flash.net.URLRequest;
	import flash.utils.ByteArray;
	
	import flexunit.framework.Assert;
	import flexunit.framework.TestCase;

	/**
	 * Test cases for the ESU API
	 */
	public class EsuApiTest extends TestCase {
		public static const TIMEOUT:int = 15000;
		
		/**
		 * Set this to your test UID
		 */
		private static const UID:String = "085e680545e8497899ae72c449462b7f/A4034851835524929c3c";
		
		/**
		 * Set this to your test UID's shared secret
		 */
		private static const SECRET:String = "nFi9d2JxlUigkLChOXh6By5CJ9I=";
		
		/**
		 * Set this to your ESU access server's hostname or IP
		 */
		private static const HOST:String = "lciga077.lss.emc.com";
		
		/**
		 * Set to your ESU access server's port.  Usually 80 or 443.
		 */
		private static const PORT:int = 80;
		
	    public var esu:EsuApi;
		public var esu2:EsuApi;
		public var cleanup:Array;
	    private var rand:Random = new Random();


		/**
		 * Creates a new connection object for each test.
		 */		
		public override function setUp():void {
			cleanup = new Array();
			esu = new EsuRestApi( HOST, PORT, UID, SECRET );
			RestLoader.traceEnabled = true;
		}
		
	    /**
	     * Tear down after a test is run.  Cleans up objects that were created
	     * during the test.
	     */
	    public override function tearDown():void {
//	        if( this.cleanup.length > 0 ) {
//	        	var cleanItem:Identifier = Identifier( cleanup.pop() );
//                this.esu.deleteObject( cleanItem, handleTearDown, cleanItem );
//	        }
	    }
	    
	    /**
	     * Responds to the delete response.  Continue looping until all
	     * objects are deleted.
	     */
	    public function handleTearDown( state:EsuCallbackState ):void {
	    	if( !state.wasSuccessful() ) {
	    		trace( "Failed to delete " + state.getStateObject() + ": " + state.getError().message );
	    	} else {
	    		trace( "Deleted: " + state.getStateObject() );
	    	}
	    	
	    	// See if there are more items to cleanup
	    	tearDown();
	    }
	    
	
	    //
	    // TESTS START HERE
	    //
	    // Since the APIs are asynchronous, each test is broken up into multiple
	    // steps with boundaries on each call to the esu API.  The addAsync function
	    // creates a new function that is registered with FlexUnit so it knows to
	    // wait for the test to complete before moving on.
	    //
	    
	    public function testChecksum():void {
	    	var ck:Checksum = new Checksum( Checksum.ALGORITHM_SHA0 );
	    	var data:ByteArray = new ByteArray();
	    	data.writeUTFBytes( "hello world" );
	    	ck.update( data );
	    	
	    	Assert.assertEquals( "checksum invalid", "SHA0/11/9fce82c34887c1953b40b3a2883e18850c4fa8a6", ck.toString() );
	    }
	    
	
	    /**
	     * Test creating one empty object.  No metadata, no content.
	     */
	    public function testCreateEmptyObject():void {
	        this.esu.createObject( null, null, null, null, addAsync( handleTestCreateEmptyObject1, TIMEOUT ), null );
	    }
	    
	    private function handleTestCreateEmptyObject1( state:EsuCallbackState ):void {
	    	checkState( state );
	    	var id:ObjectId = ObjectId( state.getResult() );
	        Assert.assertNotNull( "null ID returned", id );
			cleanup.push( id );	    	

	   		// Read back the content
	        this.esu.readObject( id, null, null, addAsync( handleTestCreateEmptyObject2, TIMEOUT ), null);

	    }
	    
	  	private function handleTestCreateEmptyObject2( state:EsuCallbackState ):void {
	    	checkState( state );
	    	var content:String = byteArrayToString( ByteArray(state.getResult() ) );
	    	Assert.assertEquals( "object content wrong", "", content );
		}
	
	    /**
	     * Test creating an object with content but without metadata
	     */
	    public function testCreateObjectWithContent():void {
			this.esu.createObject( null, null, stringToByteArray( "hello" ), "text/plain", addAsync( handleTestCreateObjectWithContent1, TIMEOUT ), null );
	
	    }
	    
	   	private function handleTestCreateObjectWithContent1( state:EsuCallbackState ):void {
	    	checkState( state );
	    	var id:ObjectId = ObjectId( state.getResult() );
	        Assert.assertNotNull( "null ID returned", id );
			cleanup.push( id );	    	

	   		// Read back the content
	        this.esu.readObject( id, null, null, addAsync( handleTestCreateObjectWithContent2, TIMEOUT ), null);

	    }

	   	private function handleTestCreateObjectWithContent2( state:EsuCallbackState ):void {
	    	checkState( state );

	        var content:String = byteArrayToString( ByteArray(state.getResult()) );
	        Assert.assertEquals( "object content wrong", "hello", content );
	   	}
	   	
	   	/**
	     * Test creating an object with content but without metadata
	     */
	    public function testCreateObjectWithChecksum():void {
	    	var checksum:Checksum = new Checksum( Checksum.ALGORITHM_SHA0 );
	        this.esu.createObject( null, null, stringToByteArray( "hello" ), "text/plain", addAsync( handleTestCreateObjectWithContent1, TIMEOUT ), null, checksum );
			trace( "Checksum: " + checksum );
	    }
	    
	    /**
	     * Tests readback with checksum verification.  In order to test this, create a policy
	     * with erasure coding and then set a policy selector with "policy=erasure" to invoke
	     * the erasure coding policy.
	     * @throws Exception
	     */
	    public function testReadObjectWithChecksum():void {
	        var mlist:MetadataList = new MetadataList();
	        var policy:Metadata = new Metadata( "policy", "erasure", false );
	        mlist.addMetadata( policy );
	    	var checksum:Checksum = new Checksum( Checksum.ALGORITHM_SHA0 );
	    	var state:Object = new Object();
	    	state.writeChecksum = checksum;
	        this.esu.createObject( null, mlist, stringToByteArray( "hello" ), "text/plain", addAsync( handleTestReadObjectWithChecksum1, TIMEOUT ), state, checksum );
	    }

	   	private function handleTestReadObjectWithChecksum1( state:EsuCallbackState ):void {
	    	checkState( state );
	    	var id:ObjectId = ObjectId( state.getResult() );
	        Assert.assertNotNull( "null ID returned", id );
			cleanup.push( id );	  
			var stateObj:Object = state.getStateObject();
			var checksum:Checksum = new Checksum(Checksum.ALGORITHM_SHA0);
			stateObj.readChecksum = checksum;	

	   		// Read back the content
	        this.esu.readObject( id, null, null, addAsync( handleTestReadObjectWithChecksum2, TIMEOUT ), stateObj, checksum );

	    }

	   	private function handleTestReadObjectWithChecksum2( state:EsuCallbackState ):void {
	    	checkState( state );

	        var content:String = byteArrayToString( ByteArray(state.getResult()) );
	        Assert.assertEquals( "object content wrong", "hello", content );
	        
	        var writeChecksum:Checksum = Checksum( state.getStateObject().writeChecksum );
	        var readChecksum:Checksum = Checksum( state.getStateObject().readChecksum );
	        
	        if( readChecksum.getExpectedValue() != null ) {
	        	// It will be null if their policy was set up wrong
	        	Assert.assertEquals( "checksums differ", writeChecksum.toString(), readChecksum.toString() );
	        } else {
	        	trace( "Please set up your policy=erasure policy to test testCreateObjectWithChecksum" );
	        }
	   	}

	    /**
	     * Tests getting object information.  For best results, create a policy that keys off
	     * policy=retaindelete and specifies both a retention and deletion date.
	     * @throws Exception
	     */
	    public function testGetObjectInfo():void {
	        var mlist:MetadataList = new MetadataList();
	        var policy:Metadata = new Metadata( "policy", "retaindelete", false );
	        mlist.addMetadata( policy );
	    	var checksum:Checksum = new Checksum( Checksum.ALGORITHM_SHA0 );
	    	var state:Object = new Object();
	    	state.writeChecksum = checksum;
	        this.esu.createObject( null, mlist, stringToByteArray( "hello" ), "text/plain", addAsync( handleTestGetObjectInfo1, TIMEOUT ), state, checksum );
	    }

	   	private function handleTestGetObjectInfo1( state:EsuCallbackState ):void {
	    	checkState( state );
	    	var id:ObjectId = ObjectId( state.getResult() );
	        Assert.assertNotNull( "null ID returned", id );
			cleanup.push( id );	  
			var stateObj:Object = state.getStateObject();
			var checksum:Checksum = new Checksum(Checksum.ALGORITHM_SHA0);
			stateObj.readChecksum = checksum;	

	   		// Read back the content
	        this.esu.getObjectInfo( id, addAsync( handleTestGetObjectInfo2, TIMEOUT ), stateObj );

	    }

	   	private function handleTestGetObjectInfo2( state:EsuCallbackState ):void {
	    	checkState( state );

	        var info:ObjectInformation = ObjectInformation( state.getResult() );
	        Assert.assertNotNull( "object info null", info );
			Assert.assertNotNull( "object info id null", info.objectId );
			Assert.assertNotNull( "object info expiration null", info.expiration );
			Assert.assertNotNull( "object info raw XML null", info.rawXml );
			Assert.assertNotNull( "object info replicas null", info.replicas );
			Assert.assertNotNull( "object info retention null", info.retention );
			Assert.assertNotNull( "object info selection null", info.selection );
			
			if( info.retention.enabled ) {
				Assert.assertNotNull( "Retention date null", info.retention.endAt );
			}
			if( info.expiration.enabled ) {
				Assert.assertNotNull( "Expiration date null", info.expiration.endAt );
			}
			
			Assert.assertTrue( "Should be more than zero replicas", info.replicas.length > 0 );
			if( info.replicas.length > 1 ) {
				Assert.assertTrue( "Replicas should have different IDs", info.replicas[0].id != info.replicas[1].id );
			}
			

	   	}

	
	    /**
	     * Test creating an object with metadata but no content.
	     */
	    public function testCreateObjectWithMetadata():void {
	        var mlist:MetadataList = new MetadataList();
	        var listable:Metadata = new Metadata( "Listable", "fOo", true );
	        var unlistable:Metadata = new Metadata( "unlistable", "Bar", false );
	        var listable2:Metadata = new Metadata( "listable2", "foo2   foo2", true );
	        var unlistable2:Metadata = new Metadata( "unlistable2", "bar2 bar2", false );
			var unicodeValue:Metadata = new Metadata( "unicodeValue", "bar2\u00A0bar2", false );
			var unicodeName:Metadata = new Metadata( "unicode\u00A0Name", "bar2 bar2", false );
	        trace( "unicode value: " + unicodeValue.getValue() );
	        trace("unicode name: " + unicodeName.getName() );
	        mlist.addMetadata( listable );
	        mlist.addMetadata( unlistable );
	        mlist.addMetadata( listable2 );
	        mlist.addMetadata( unlistable2 );
			mlist.addMetadata(unicodeValue);
			mlist.addMetadata( unicodeName );
	        this.esu.createObject( null, mlist, null, null, addAsync( handleTestCreateObjectWithMetadata1, TIMEOUT ), null );
	    }
	    
	    private function handleTestCreateObjectWithMetadata1( state:EsuCallbackState ):void {
	    	checkState( state );
	    	var id:ObjectId = ObjectId( state.getResult() );
	        Assert.assertNotNull( "null ID returned", id );
			cleanup.push( id );	    	

	   		// Read back the metadata
	        this.esu.getUserMetadata( id, null, addAsync( handleTestCreateObjectWithMetadata2, TIMEOUT ), null);
	    }
	    
	    private function handleTestCreateObjectWithMetadata2( state:EsuCallbackState ):void {
	    	checkState( state );
	    	var meta:MetadataList = MetadataList( state.getResult() );
	        Assert.assertEquals( "value of 'Listable' wrong", "fOo", meta.getMetadata( "Listable" ).getValue() );
	        Assert.assertEquals( "value of 'listable2' wrong", "foo2   foo2", meta.getMetadata( "listable2" ).getValue() );
	        Assert.assertEquals( "value of 'unlistable' wrong", "Bar", meta.getMetadata( "unlistable" ).getValue() );
	        Assert.assertEquals( "value of 'unlistable2' wrong", "bar2 bar2", meta.getMetadata( "unlistable2" ).getValue() );
	        Assert.assertEquals( "value of 'unicodeValue' wrong", "bar2?bar2", meta.getMetadata( "unicodeValue" ).getValue() );
	        Assert.assertEquals( "value of 'unicode?Name' wrong", "bar2 bar2", meta.getMetadata( "unicode?Name" ).getValue() );
	    	
	    }
	
	    /**
	     * Test handling signature failures.  Should throw an exception with
	     * error code 1032.
	     */
	    public function testSignatureFailure():void {
	        // break the secret key
	        var badSecret:String = SECRET.toUpperCase();
	        this.esu = new EsuRestApi( HOST, PORT, UID, badSecret );
	
            this.esu.createObject( null, null, null, null, addAsync( handleTestSignatureFailureResponse, TIMEOUT ), null );
	    }
	    
	    public function handleTestSignatureFailureResponse( state:EsuCallbackState ):void {
	    	Assert.assertFalse( state.wasSuccessful()  );
	    	Assert.assertTrue( "Error code should be 1032 or 403", state.getError().errorID == 403 || state.getError().errorID == 1032 );
	    }
	
	    /**
	     * Test general HTTP errors by generating a 404.
	     */
	    public function testFourOhFour():void {
	        // break the context root
			if( this.esu is EsuRestApi ) {
	        	EsuRestApi(this.esu).setContext( "/restttt" );
			} else if( this.esu is EsuRestApiHttp ) {
				EsuRestApiHttp(this.esu).setContext( "/restttt" );
			} else {
				// Skip
				return;
			}
	        this.esu.createObject( null, null, null, null, addAsync( handleTestFourOhFourResponse, TIMEOUT ), null );
	    }
	    
	    private function handleTestFourOhFourResponse( state:EsuCallbackState ):void {
	    	Assert.assertFalse( state.wasSuccessful()  );
	    	Assert.assertEquals( "Error code should be 404", 404, state.getError().errorID  );
	    }
	
	    /**
	     * Test reading an object's content
	     */
	    public function testReadObject():void {
	        this.esu.createObject( null, null, stringToByteArray( "hello" ), "text/plain",
	        	addAsync( handleTestReadObjectResponse1, TIMEOUT ), null );
	    }

	    
	    private function handleTestReadObjectResponse1( state:EsuCallbackState ):void {
	    	checkState( state );
	    	var id:ObjectId = ObjectId( state.getResult() );
	    	Assert.assertNotNull( "null ID returned", id );
	        cleanup.push( id );

			// Read back the content
	        this.esu.readObject( id, null, null, addAsync( handleTestReadObjectResponse2, TIMEOUT ), id );

	    }
	    
	    private function handleTestReadObjectResponse2( state:EsuCallbackState ):void {
	    	checkState( state );
	    	var bytes:ByteArray = ByteArray( state.getResult() );
	    	var content:String = bytes.readUTFBytes( bytes.length );
	        Assert.assertEquals( "object content wrong", "hello", content );
	
	        // Read back only 2 bytes
	        var extent:Extent = new Extent( 1, 2 );
	        var id:ObjectId = ObjectId( state.getStateObject() );
	        this.esu.readObject( id, extent, null, addAsync( handleTestReadObjectResponse3, TIMEOUT ), id );
	                  
	    }
	    
	    private function handleTestReadObjectResponse3( state:EsuCallbackState ):void {
	    	checkState( state );
	    	var bytes:ByteArray = ByteArray( state.getResult() );
	    	var content:String = bytes.readUTFBytes( bytes.length );
	    	Assert.assertEquals( "partial object content wrong", "el", content );
	    }
	    
	
	    /**
	     * Test reading an ACL back
	     */
	    public function testReadAcl():void {
	        // Create an object with an ACL
	        var acl:Acl = new Acl();
	        acl.addGrant( new Grant( new Grantee( UID, Grantee.GRANT_TYPE_USER ), Permission.FULL_CONTROL ) );
	        acl.addGrant( new Grant( Grantee.OTHER, Permission.READ ) );
	        this.esu.createObject( acl, null, null, null,
	        	addAsync( testHandleReadAclResponse1, TIMEOUT ), acl );
	
	    }
	    
	    private function testHandleReadAclResponse1( state:EsuCallbackState ):void {
	    	checkState( state );
	    	var id:ObjectId = ObjectId( state.getResult() );
	        Assert.assertNotNull( "null ID returned", id );
	        cleanup.push( id );
	        
	        var acl:Acl = Acl( state.getStateObject() );
	
	        // Read back the ACL and make sure it matches
	        this.esu.getAcl( id, addAsync( testHandleReadAclResponse2, TIMEOUT ), acl );
	
	    	
	    }
	    private function testHandleReadAclResponse2( state:EsuCallbackState ):void {
	    	checkState( state );
	        var acl:Acl = Acl( state.getStateObject() );
			var newacl:Acl = Acl( state.getResult() );	    	
	        trace( "Comparing " + newacl + " with " + acl );
	        Assert.assertTrue( acl.equals( newacl ) );
	    	
	    }
	
	    /**
	     * Test reading back user metadata
	     */
	    public function testGetUserMetadata():void {
	        // Create an object with user metadata
	        var mlist:MetadataList = new MetadataList();
	        var listable:Metadata = new Metadata( "listable", "foo", true );
	        var unlistable:Metadata = new Metadata( "unlistable", "bar", false );
	        var listable2:Metadata = new Metadata( "listable2", "foo2 foo2", true );
	        var unlistable2:Metadata = new Metadata( "unlistable2", "bar2 bar2", false );
	        mlist.addMetadata( listable );
	        mlist.addMetadata( unlistable );
	        mlist.addMetadata( listable2 );
	        mlist.addMetadata( unlistable2 );
	        this.esu.createObject( null, mlist, null, null,
	        	addAsync( handleTestGetUserMetadataResponse1, TIMEOUT ), null );
	
	
	    }
	    
	    private function handleTestGetUserMetadataResponse1( state:EsuCallbackState ):void {
	    	checkState( state );
	    	var id:ObjectId = ObjectId( state.getResult() );
	        Assert.assertNotNull( "null ID returned", id );
	        cleanup.push( id );
	        
	        // Read only part of the metadata
	        var mtags:MetadataTags = new MetadataTags();
	        mtags.addTag( new MetadataTag( "listable", true ) );
	        mtags.addTag( new MetadataTag( "unlistable", false ) );
	    	this.esu.getUserMetadata( id, mtags, addAsync( handleTestGetUserMetadataResponse2, TIMEOUT ), null );
	    }
	    
	    private function handleTestGetUserMetadataResponse2( state:EsuCallbackState ):void {
	    	checkState( state );
	        var meta:MetadataList = MetadataList( state.getResult() );
	        Assert.assertEquals( "value of 'listable' wrong", "foo", meta.getMetadata( "listable" ).getValue() );
	        Assert.assertNull( "value of 'listable2' should not have been returned", meta.getMetadata( "listable2" ) );
	        Assert.assertEquals( "value of 'unlistable' wrong", "bar", meta.getMetadata( "unlistable" ).getValue() );
	        Assert.assertNull( "value of 'unlistable2' should not have been returned", meta.getMetadata( "unlistable2" ) );
	    }
	    
	
	    /**
	     * Test deleting user metadata
	     */
	    public function testDeleteUserMetadata():void {
	        // Create an object with metadata
	        var mlist:MetadataList = new MetadataList();
	        var listable:Metadata = new Metadata( "listable", "foo", true );
	        var unlistable:Metadata = new Metadata( "unlistable", "bar", false );
	        var listable2:Metadata = new Metadata( "listable2", "foo2 foo2", true );
	        var unlistable2:Metadata = new Metadata( "unlistable2", "bar2 bar2", false );
	        mlist.addMetadata( listable );
	        mlist.addMetadata( unlistable );
	        mlist.addMetadata( listable2 );
	        mlist.addMetadata( unlistable2 );
	        this.esu.createObject( null, mlist, null, null, addAsync( handleTestDeleteUserMetadata1, TIMEOUT ), null );
	    }
	    
	    private function handleTestDeleteUserMetadata1( state:EsuCallbackState ):void {
	    	checkState( state );
	    	var id:ObjectId = ObjectId( state.getResult() );
	        Assert.assertNotNull( "null ID returned", id );
	        cleanup.push( id );
	        
	        // Delete a couple of the metadata entries
	        var mtags:MetadataTags = new MetadataTags();
	        mtags.addTag( new MetadataTag( "listable2", true ) );
	        mtags.addTag( new MetadataTag( "unlistable2", false ) );
	        this.esu.deleteUserMetadata( id, mtags, addAsync( handleTestDeleteUserMetadata2, TIMEOUT ), id );
	    }
	    
	    private function handleTestDeleteUserMetadata2( state:EsuCallbackState ):void {
	    	var id:ObjectId = ObjectId( state.getStateObject() );
	        // Read back the metadata for the object and ensure the deleted
	        // entries don't exist
	        this.esu.getUserMetadata( id, null, addAsync( handleTestDeleteUserMetadata3, TIMEOUT ), id );
	    }
	    
	    private function handleTestDeleteUserMetadata3( state:EsuCallbackState ):void {
	    	var id:ObjectId = ObjectId( state.getStateObject() );
	        var meta:MetadataList = MetadataList( state.getResult() );
	        Assert.assertEquals( "value of 'listable' wrong", "foo", meta.getMetadata( "listable" ).getValue() );
	        Assert.assertNull( "value of 'listable2' should not have been returned", meta.getMetadata( "listable2" ) );
	        Assert.assertEquals( "value of 'unlistable' wrong", "bar", meta.getMetadata( "unlistable" ).getValue() );
	        Assert.assertNull( "value of 'unlistable2' should not have been returned", meta.getMetadata( "unlistable2" ) );
	    }
	    
	
	    /**
	     * Test creating object versions
	     */
	    public function testVersionObject():void {
	        // Create an object
	        var mlist:MetadataList = new MetadataList();
	        var listable:Metadata = new Metadata( "listable", "foo", true );
	        var unlistable:Metadata = new Metadata( "unlistable", "bar", false );
	        var listable2:Metadata = new Metadata( "listable2", "foo2 foo2", true );
	        var unlistable2:Metadata = new Metadata( "unlistable2", "bar2 bar2", false );
	        mlist.addMetadata( listable );
	        mlist.addMetadata( unlistable );
	        mlist.addMetadata( listable2 );
	        mlist.addMetadata( unlistable2 );
	        this.esu.createObject( null, mlist, null, null, addAsync( handleTestVersionObject1, TIMEOUT ), null );
	    }
	    
	    private function handleTestVersionObject1( state:EsuCallbackState ):void {
	    	checkState( state );
	    	var id:ObjectId = ObjectId( state.getResult() );
	        Assert.assertNotNull( "null ID returned", id );
	        cleanup.push( id );
	    	this.esu.versionObject( id, addAsync( handleTestVersionObject2, TIMEOUT ), id );
	    }
	    
	    private function handleTestVersionObject2( state:EsuCallbackState ):void {
	        // Version the object
	        var vid:ObjectId = ObjectId( state.getResult() );
	        var id:ObjectId = ObjectId( state.getStateObject() );
	        cleanup.push( vid );
	        Assert.assertNotNull( "null version ID returned", vid );
	        
	        Assert.assertFalse( "Version ID shoudn't be same as original ID", id.equals(vid) );
	        this.esu.getUserMetadata( vid, null, addAsync( handleTestVersionObject3, TIMEOUT ), null );
	    	
	    }
	    
	    private function handleTestVersionObject3( state:EsuCallbackState ):void {
	        // Fetch the version and read its data
	        var meta:MetadataList = MetadataList( state.getResult() );
	        Assert.assertEquals( "value of 'listable' wrong", "foo", meta.getMetadata( "listable" ).getValue() );
	        Assert.assertEquals( "value of 'listable2' wrong", "foo2 foo2", meta.getMetadata( "listable2" ).getValue() );
	        Assert.assertEquals( "value of 'unlistable' wrong", "bar", meta.getMetadata( "unlistable" ).getValue() );
	        Assert.assertEquals( "value of 'unlistable2' wrong", "bar2 bar2", meta.getMetadata( "unlistable2" ).getValue() );
	    	
	    }
	
	    /**
	     * Test listing the versions of an object
	     */
	    public function testListVersions():void {
	        // Create an object
	        var mlist:MetadataList = new MetadataList();
	        var listable:Metadata = new Metadata( "listable", "foo", true );
	        var unlistable:Metadata = new Metadata( "unlistable", "bar", false );
	        var listable2:Metadata = new Metadata( "listable2", "foo2 foo2", true );
	        var unlistable2:Metadata = new Metadata( "unlistable2", "bar2 bar2", false );
	        mlist.addMetadata( listable );
	        mlist.addMetadata( unlistable );
	        mlist.addMetadata( listable2 );
	        mlist.addMetadata( unlistable2 );
	        this.esu.createObject( null, mlist, null, null,
	        	addAsync( handleTestListVersionsResponse1, TIMEOUT ), null );
	    }
	    
	    private function handleTestListVersionsResponse1( state:EsuCallbackState ):void {
	    	checkState( state );
	    	var id:ObjectId = ObjectId( state.getResult() );
	        Assert.assertNotNull( "null ID returned", id );
	        cleanup.push( id );
	        
	        var stateObj:Object = new Object();
	        stateObj["id"] = id;
	    	
	        // Version the object
	        this.esu.versionObject( id, addAsync( handleTestListVersionsResponse2, TIMEOUT ), stateObj );
	    }
	    private function handleTestListVersionsResponse2( state:EsuCallbackState ):void {
	    	checkState( state );
	    	var vid1:ObjectId = ObjectId( state.getResult() );
	        Assert.assertNotNull( "null ID returned", vid1 );
	        cleanup.push( vid1 );
	        
	        var stateObject:Object = state.getStateObject();
	        stateObject["vid1"] = vid1;
	        
	        // Version the object again
	        this.esu.versionObject( ObjectId( stateObject["id"] ), 
	        	addAsync( handleTestListVersionsResponse3, TIMEOUT ), stateObject );
	    }
	    private function handleTestListVersionsResponse3( state:EsuCallbackState ):void {
	    	checkState( state );
	    	var vid2:ObjectId = ObjectId( state.getResult() );
	        Assert.assertNotNull( "null ID returned", vid2 );
	        cleanup.push( vid2 );
	        
	        var stateObject:Object = state.getStateObject();
	        stateObject["vid2"] = vid2;
	    	
	    	// List the versions and ensure their IDs are correct
	    	this.esu.listVersions( ObjectId(stateObject["id"] ), 
	    		addAsync( handleTestListVersionsResponse4, TIMEOUT ), stateObject );
	    }
	    private function handleTestListVersionsResponse4( state:EsuCallbackState ):void {
	    	checkState( state );
	        var versions:Array = state.getResult() as Array;
	        var stateObject:Object = state.getStateObject();
	        
	        Assert.assertEquals( "Wrong number of versions returned", 2, versions.length );
	        Assert.assertTrue( "version 1 not found in version list", 
	        	ObjectId.findInArray( stateObject["vid1"] , versions ) != -1 );
	        Assert.assertTrue( "version 2 not found in version list", 
	        	ObjectId.findInArray( stateObject["vid2"] , versions ) != -1 );
	        Assert.assertFalse( "base object found in version list", 
	        	ObjectId.findInArray( stateObject["id"] , versions ) != -1 );
	    }

	    /**
	     * Test listing the versions of an object
	     */
	    public function testDeleteVersions():void {
	        // Create an object
	        var mlist:MetadataList = new MetadataList();
	        var listable:Metadata = new Metadata( "listable", "foo", true );
	        var unlistable:Metadata = new Metadata( "unlistable", "bar", false );
	        var listable2:Metadata = new Metadata( "listable2", "foo2 foo2", true );
	        var unlistable2:Metadata = new Metadata( "unlistable2", "bar2 bar2", false );
	        mlist.addMetadata( listable );
	        mlist.addMetadata( unlistable );
	        mlist.addMetadata( listable2 );
	        mlist.addMetadata( unlistable2 );
	        this.esu.createObject( null, mlist, null, null,
	        	addAsync( handleTestDeleteVersionsResponse1, TIMEOUT ), null );
	    }
	    
	    private function handleTestDeleteVersionsResponse1( state:EsuCallbackState ):void {
	    	checkState( state );
	    	var id:ObjectId = ObjectId( state.getResult() );
	        Assert.assertNotNull( "null ID returned", id );
	        cleanup.push( id );
	        
	        var stateObj:Object = new Object();
	        stateObj["id"] = id;
	    	
	        // Version the object
	        this.esu.versionObject( id, addAsync( handleTestDeleteVersionsResponse2, TIMEOUT ), stateObj );
	    }
	    private function handleTestDeleteVersionsResponse2( state:EsuCallbackState ):void {
	    	checkState( state );
	    	var vid1:ObjectId = ObjectId( state.getResult() );
	        Assert.assertNotNull( "null ID returned", vid1 );
	        cleanup.push( vid1 );
	        
	        var stateObject:Object = state.getStateObject();
	        stateObject["vid1"] = vid1;
	        
	        // Version the object again
	        this.esu.versionObject( ObjectId( stateObject["id"] ), 
	        	addAsync( handleTestDeleteVersionsResponse3, TIMEOUT ), stateObject );
	    }
	    
	    private function handleTestDeleteVersionsResponse3( state:EsuCallbackState):void {
	    	checkState( state );
	    	var vid2:ObjectId = ObjectId( state.getResult() );
	        Assert.assertNotNull( "null ID returned", vid2 );
	        cleanup.push( vid2 );
	        
	        var stateObject:Object = state.getStateObject();
	        stateObject["vid2"] = vid2;
	        var v1id:ObjectId = stateObject["vid1"];

			// Delete a version
			this.esu.deleteVersion( v1id, addAsync( handleTestDeleteVersionsResponse4, TIMEOUT ), stateObject );		    	
	    }
	    private function handleTestDeleteVersionsResponse4( state:EsuCallbackState ):void {
	    	checkState( state );
	        var stateObject:Object = state.getStateObject();
	    	
	    	// List the versions and ensure their IDs are correct
	    	this.esu.listVersions( ObjectId(stateObject["id"] ), 
	    		addAsync( handleTestDeleteVersionsResponse5, TIMEOUT ), stateObject );
	    }
	    private function handleTestDeleteVersionsResponse5( state:EsuCallbackState ):void {
	    	checkState( state );
	        var versions:Array = state.getResult() as Array;
	        var stateObject:Object = state.getStateObject();
	        
	        Assert.assertEquals( "Wrong number of versions returned", 1, versions.length );
	        Assert.assertFalse( "version 1 found in version list", 
	        	ObjectId.findInArray( stateObject["vid1"] , versions ) != -1 );
	        Assert.assertTrue( "version 2 not found in version list", 
	        	ObjectId.findInArray( stateObject["vid2"] , versions ) != -1 );
	        Assert.assertFalse( "base object found in version list", 
	        	ObjectId.findInArray( stateObject["id"] , versions ) != -1 );
	    }
	    
	    /**
	     * Test restoring the versions of an object
	     */
	    public function testRestoreVersions():void {
	        // Create an object
	        var mlist:MetadataList = new MetadataList();
	        var listable:Metadata = new Metadata( "listable", "foo", true );
	        var unlistable:Metadata = new Metadata( "unlistable", "bar", false );
	        var listable2:Metadata = new Metadata( "listable2", "foo2 foo2", true );
	        var unlistable2:Metadata = new Metadata( "unlistable2", "bar2 bar2", false );
	        mlist.addMetadata( listable );
	        mlist.addMetadata( unlistable );
	        mlist.addMetadata( listable2 );
	        mlist.addMetadata( unlistable2 );
	        this.esu.createObject( null, mlist, stringToByteArray("four score and seven years ago"), null,
	        	addAsync( handleTestRestoreVersionsResponse1, TIMEOUT ), null );
	    }
	    
	    private function handleTestRestoreVersionsResponse1( state:EsuCallbackState ):void {
	    	checkState( state );
	    	var id:ObjectId = ObjectId( state.getResult() );
	        Assert.assertNotNull( "null ID returned", id );
	        cleanup.push( id );
	        
	        var stateObj:Object = new Object();
	        stateObj["id"] = id;
	    	
	        // Version the object
	        this.esu.versionObject( id, addAsync( handleTestRestoreVersionsResponse2, TIMEOUT ), stateObj );
	    }
	    private function handleTestRestoreVersionsResponse2( state:EsuCallbackState ):void {
	    	checkState( state );
	    	var vid1:ObjectId = ObjectId( state.getResult() );
	        Assert.assertNotNull( "null ID returned", vid1 );
	        cleanup.push( vid1 );
	        
	        var stateObject:Object = state.getStateObject();
	        stateObject["vid1"] = vid1;
	        
	        // Change the content
	        this.esu.updateObject( ObjectId( stateObject["id"] ), null,null, null, stringToByteArray( "You should not see me" ), null, 
	        	addAsync( handleTestRestoreVersionsResponse3, TIMEOUT ), stateObject );
	    }
	    
	    private function handleTestRestoreVersionsResponse3( state:EsuCallbackState):void {
	    	checkState( state );
	        
	        var stateObject:Object = state.getStateObject();
	        var id:ObjectId = stateObject["id"];
	        var v1id:ObjectId = stateObject["vid1"];

			// Restore the old version
			this.esu.restoreVersion( id, v1id, addAsync( handleTestRestoreVersionsResponse4, TIMEOUT ), stateObject );		    	
	    }
	    private function handleTestRestoreVersionsResponse4( state:EsuCallbackState ):void {
	    	checkState( state );
	        var stateObject:Object = state.getStateObject();
	    	
	    	// Read the content back
	    	this.esu.readObject( ObjectId(stateObject["id"] ), null, null,
	    		addAsync( handleTestRestoreVersionsResponse5, TIMEOUT ), stateObject );
	    }
	    private function handleTestRestoreVersionsResponse5( state:EsuCallbackState ):void {
	    	checkState( state );
	    	var bytes:ByteArray = ByteArray( state.getResult() );
	    	var content:String = bytes.readUTFBytes( bytes.length );
	        
	    	Assert.assertEquals( "object content wrong", "four score and seven years ago", content );
	    }
	
	    /**
	     * Test listing the system metadata on an object
	     */
	    public function testGetSystemMetadata():void {
	        // Create an object
	        var mlist:MetadataList = new MetadataList();
	        var listable:Metadata = new Metadata( "listable", "foo", true );
	        var unlistable:Metadata = new Metadata( "unlistable", "bar", false );
	        var listable2:Metadata = new Metadata( "listable2", "foo2 foo2", true );
	        var unlistable2:Metadata = new Metadata( "unlistable2", "bar2 bar2", false );
	        mlist.addMetadata( listable );
	        mlist.addMetadata( unlistable );
	        mlist.addMetadata( listable2 );
	        mlist.addMetadata( unlistable2 );
	        this.esu.createObject( null, mlist, null, null, addAsync( handleTestGetSystemMetadataResponse1, TIMEOUT ), null );
	    }
	    
	    private function handleTestGetSystemMetadataResponse1( state:EsuCallbackState ):void {
	    	checkState( state );
	    	var id:ObjectId = ObjectId( state.getResult() );
	        Assert.assertNotNull( "null ID returned", id );
	        cleanup.push( id );
	        
	        // Read only part of the metadata
	        var mtags:MetadataTags = new MetadataTags();
	        mtags.addTag( new MetadataTag( "atime", false ) );
	        mtags.addTag( new MetadataTag( "ctime", false ) );
	    	this.esu.getSystemMetadata( id, mtags, addAsync( handleTestGetSystemMetadataResponse2, TIMEOUT ), null );
	    }
	    
	    private function handleTestGetSystemMetadataResponse2( state:EsuCallbackState ):void {
	    	checkState( state );
	        var meta:MetadataList = MetadataList( state.getResult() );
	        Assert.assertNotNull( "value of 'atime' missing", meta.getMetadata( "atime" ) );
	        Assert.assertNull( "value of 'mtime' should not have been returned", meta.getMetadata( "mtime" ) );
	        Assert.assertNotNull( "value of 'ctime' missing", meta.getMetadata( "ctime" ) );
	        Assert.assertNull( "value of 'gid' should not have been returned", meta.getMetadata( "gid" ) );
	        Assert.assertNull( "value of 'listable' should not have been returned", meta.getMetadata( "listable" ) );
	    }
	    
//	   	/**
//	     * Test listing objects by a tag
//	     */
//	    public function testListObjectsWithMeta():void {
//	        // Create an object
//	        var mlist:MetadataList = new MetadataList();
//	        var listable:Metadata = new Metadata( "listable", "foo", true );
//	        var unlistable:Metadata = new Metadata( "unlistable", "bar", false );
//	        var listable2:Metadata = new Metadata( "listable2", "foo2 foo2", true );
//	        var unlistable2:Metadata = new Metadata( "unlistable2", "bar2 bar2", false );
//	        mlist.addMetadata( listable );
//	        mlist.addMetadata( unlistable );
//	        mlist.addMetadata( listable2 );
//	        mlist.addMetadata( unlistable2 );
//	        this.esu.createObject( null, mlist, null, null, addAsync( handleTestListObjectsWithMetaRespnose1, TIMEOUT ), null );
//	    }
//	    
//	   	private function handleTestListObjectsWithMetaRespnose1( state:EsuCallbackState ):void {
//	    	checkState( state );
//	    	var id:ObjectId = ObjectId( state.getResult() );
//	        Assert.assertNotNull( "null ID returned", id );
//	        cleanup.push( id );
//	        
//	        // List the objects.  Make sure the one we created is in the list
//			var options:ListOptions = new ListOptions();
//			options.includeMetadata = true;
//	        this.esu.listObjects( "listable", options, addAsync( handleTestListObjectsWithMetaResponse2, TIMEOUT ), id );	    	
//	    }
//	    
//	    private function handleTestListObjectsWithMetaResponse2( state:EsuCallbackState ):void {
//	    	checkState( state );
//	    	var objects:Array = state.getResult() as Array;
//	    	var id:ObjectId = ObjectId( state.getStateObject() );
//	    	
//	    	var found:Boolean = false;
//	    	for each( var o:Object in objects ) {
//	    		if( id.equals( o.id ) ) {
//	    			found = true;
//	    			// Look for metadata
//	    			var meta:MetadataList = MetadataList( o.meta );
//	    			Assert.assertEquals( "Wrong value", "foo", meta.getMetadata( "listable" ).getValue() );
//	    		}
//	    	}
//	    	
//	        Assert.assertTrue( "No objects returned", objects.length > 0 );
//	        Assert.assertTrue( "object not found in list" , found );
//	    }

	    /**
	     * Test get service information
	     */
	    public function testGetServiceInformation():void {

	        this.esu.getServiceInformation( addAsync( handleTestGetServiceInformation1, TIMEOUT ), null );
	    }

	    private function handleTestGetServiceInformation1( state:EsuCallbackState ):void {
	    	checkState( state );
			var serviceInfo:ServiceInformation = ServiceInformation( state.getResult() );
			trace( "Atmos Version: " + serviceInfo.atmosVersion );
			Assert.assertNotNull( "Atmos version null", serviceInfo.atmosVersion );
	    }	    
	    
	    /**
	     * Test listing objects by a tag
	     */
	    public function testListObjects():void {
	        // Create an object
	        var mlist:MetadataList = new MetadataList();
	        var listable:Metadata = new Metadata( "listable", "foo", true );
	        var unlistable:Metadata = new Metadata( "unlistable", "bar", false );
	        var listable2:Metadata = new Metadata( "listable2", "foo2 foo2", true );
	        var unlistable2:Metadata = new Metadata( "unlistable2", "bar2 bar2", false );
	        mlist.addMetadata( listable );
	        mlist.addMetadata( unlistable );
	        mlist.addMetadata( listable2 );
	        mlist.addMetadata( unlistable2 );
	        this.esu.createObject( null, mlist, null, null, addAsync( handleTestListObjectsRespnose1, TIMEOUT ), null );
	    }
	    
	    private function handleTestListObjectsRespnose1( state:EsuCallbackState ):void {
	    	checkState( state );
	    	var id:ObjectId = ObjectId( state.getResult() );
	        Assert.assertNotNull( "null ID returned", id );
	        cleanup.push( id );
	        
	        // List the objects.  Make sure the one we created is in the list
	        this.esu.listObjects( "listable", null, addAsync( handleTestListObjectsResponse2, TIMEOUT ), id );	    	
	    }
	    
	    private function handleTestListObjectsResponse2( state:EsuCallbackState ):void {
	    	checkState( state );
	    	var objects:Array = state.getResult() as Array;
	    	var id:ObjectId = ObjectId( state.getStateObject() );
	    	
	        Assert.assertTrue( "No objects returned", objects.length > 0 );
	        Assert.assertTrue( "object not found in list" , ObjectId.findInArray( id, objects ) != -1 );
	    	
	        // Check for unlisted
	        this.esu.listObjects( "unlistable", null, addAsync( handleTestListObjectsResponse3, TIMEOUT ), null );
	    }
	    
	    private function handleTestListObjectsResponse3( state:EsuCallbackState ):void {
	    	
	    	Assert.assertFalse( "Request should have failed", state.wasSuccessful() );
	    	Assert.assertTrue( "Expected 1003 or 404 for not found", state.getError().getCode() == 1003 || state.getError().getCode() == 404 );
	    }
	    
	
	    /**
	     * Test fetching listable tags
	     */
	    public function testGetListableTags():void {
	        // Create an object
	        var mlist:MetadataList = new MetadataList();
	        var listable:Metadata = new Metadata( "listable", "foo", true );
	        var unlistable:Metadata = new Metadata( "unlistable", "bar", false );
	        var listable2:Metadata = new Metadata( "list/able/2", "foo2 foo2", true );
	        var unlistable2:Metadata = new Metadata( "list/able/not", "bar2 bar2", false );
	        mlist.addMetadata( listable );
	        mlist.addMetadata( unlistable );
	        mlist.addMetadata( listable2 );
	        mlist.addMetadata( unlistable2 );
	        this.esu.createObject( null, mlist, null, null, 
	        	addAsync( handleTestGetListableTagsResponse1, TIMEOUT ), null );
	    }
	    
	    
	    private function handleTestGetListableTagsResponse1( state:EsuCallbackState ):void {
	    	checkState( state );
	    	var id:ObjectId = ObjectId( state.getResult() );
	        Assert.assertNotNull( "null ID returned", id );
	        cleanup.push( id );
	    	
	    	this.esu.getListableTags( null, addAsync( handleTestGetListableTagsResponse2, TIMEOUT ), id );
	    }
	    private function handleTestGetListableTagsResponse2( state:EsuCallbackState ):void {
	    	checkState( state );
	    	var tags:MetadataTags = MetadataTags( state.getResult() );
	    	var id:ObjectId = ObjectId( state.getStateObject() );

	        // List tags.  Ensure our object's tags are in the list.
	        Assert.assertTrue( "listable tag not returned", tags.contains( "listable" ) );
	        Assert.assertTrue( "list/able/2 root tag not returned", tags.contains( "list" ) );
	        Assert.assertFalse( "list/able/not tag returned", tags.contains( "list/able/not" ) );
	        
	        // List child tags
	        this.esu.getListableTags( "list/able", addAsync( handleTestGetListableTagsResponse3, TIMEOUT ), id );
	    }
	    private function handleTestGetListableTagsResponse3( state:EsuCallbackState ):void {
	    	checkState( state );
	    	var tags:MetadataTags = MetadataTags( state.getResult() );
	    	var id:ObjectId = ObjectId( state.getStateObject() );

	        Assert.assertFalse( "non-child returned", tags.contains( "listable" ) );
	        Assert.assertTrue( "list/able/2 tag not returned", tags.contains( "2" ) );
	        Assert.assertFalse( "list/able/not tag returned", tags.contains( "not" ) );
	    }
	    
	
	    /**
	     * Test listing the user metadata tags on an object
	     */
	    public function testListUserMetadataTags():void {
	        // Create an object
	        var mlist:MetadataList = new MetadataList();
	        var listable:Metadata = new Metadata( "listable", "foo", true );
	        var unlistable:Metadata = new Metadata( "unlistable", "bar", false );
	        var listable2:Metadata = new Metadata( "list/able/2", "foo2 foo2", true );
	        var unlistable2:Metadata = new Metadata( "list/able/not", "bar2 bar2", false );
	        mlist.addMetadata( listable );
	        mlist.addMetadata( unlistable );
	        mlist.addMetadata( listable2 );
	        mlist.addMetadata( unlistable2 );
	        this.esu.createObject( null, mlist, null, null, 
	        	addAsync( handleTestListUserMetadataTagsResponse1, TIMEOUT ), null );
	    }
	    
	    private function handleTestListUserMetadataTagsResponse1( state:EsuCallbackState ):void {
	    	checkState( state );
	    	var id:ObjectId = ObjectId( state.getResult() );
	        Assert.assertNotNull( "null ID returned", id );
	        cleanup.push( id );
	    	
	    	this.esu.listUserMetadataTags( id, 
	    		addAsync( handleTestListUserMetadataTagsResponse2, TIMEOUT ), null );
	    }
	    
	    private function handleTestListUserMetadataTagsResponse2( state:EsuCallbackState ):void {
	    	checkState( state );
	    	
	    	var tags:MetadataTags = MetadataTags( state.getResult() );
	    	
	        Assert.assertTrue( "listable tag not returned", tags.contains( "listable" ) );
	        Assert.assertTrue( "list/able/2 tag not returned", tags.contains( "list/able/2" ) );
	        Assert.assertTrue( "unlistable tag not returned", tags.contains( "unlistable" ) );
	        Assert.assertTrue( "list/able/not tag not returned", tags.contains( "list/able/not" ) );
	        Assert.assertFalse( "unknown tag returned", tags.contains( "unknowntag" ) );
	    	
	        // Check listable flag
	        Assert.assertEquals( "'listable' is not listable", true, tags.getTag( "listable" ).isListable() );
	        Assert.assertEquals( "'list/able/2' is not listable", true, tags.getTag( "list/able/2" ).isListable() );
	        Assert.assertEquals( "'unlistable' is listable", false, tags.getTag( "unlistable" ).isListable() );
	        Assert.assertEquals( "'list/able/not' is listable", false, tags.getTag( "list/able/not" ).isListable() );
	    }
	    

//
// Not currently supported (as of Atmos 1.1)
//	
//	    /**
//	     * Test executing a query.
//	     */
//	    public function testQueryObjects():void {
//	        // Create an object
//	        var mlist:MetadataList = new MetadataList();
//	        var listable:Metadata = new Metadata( "listable", "foo", true );
//	        var unlistable:Metadata = new Metadata( "unlistable", "bar", false );
//	        var listable2:Metadata = new Metadata( "list/able/2", "foo2 foo2", true );
//	        var unlistable2:Metadata = new Metadata( "list/able/not", "bar2 bar2", false );
//	        mlist.addMetadata( listable );
//	        mlist.addMetadata( unlistable );
//	        mlist.addMetadata( listable2 );
//	        mlist.addMetadata( unlistable2 );
//	        this.esu.createObject( null, mlist, null, null,
//	        	addAsync( handleTestQueryObjectsResponse1, TIMEOUT ), null );
//	    }
	    
	    private function handleTestQueryObjectsResponse1( state:EsuCallbackState ):void {
	    	checkState( state );
	    	var id:ObjectId = ObjectId( state.getResult() );
	        Assert.assertNotNull( "null ID returned", id );
	        cleanup.push( id );
	        
	        // Query for all objects for the current UID
	        var query:String = "for $h in collection() where $h/maui:MauiObject[uid=\"" +
	            UID + "\"] return $h";
	        trace( "Query: " + query );
	        this.esu.queryObjects( query, addAsync( handleTestQueryObjectsResponse2, TIMEOUT ), id );
	    }
	    
	    private function handleTestQueryObjectsResponse2( state:EsuCallbackState ):void {
	    	checkState( state );
	    	
	    	var objects:Array = state.getResult() as Array;
	    	var id:ObjectId = ObjectId( state.getStateObject() );

	    	// Ensure the search results contains the object we just created
	        Assert.assertTrue( "object not found in list", ObjectId.findInArray( id, objects ) != -1 );
	    }
	
	    /**
	     * Tests updating an object's metadata
	     */
	    public function testUpdateObjectMetadata():void {
	        // Create an object
	        var mlist:MetadataList = new MetadataList();
	        var unlistable:Metadata = new Metadata( "unlistable", "foo", false );
	        mlist.addMetadata( unlistable );
	        var stateObject:Object = new Object();
	        stateObject["mlist"] = mlist;
	        stateObject["unlistable"] = unlistable;
	        
	        this.esu.createObject( null, mlist, stringToByteArray( "hello" ), null,
	        	addAsync( handleTestUpdateObjectMetadataResponse1, TIMEOUT ), stateObject );
	    }    
	    private function handleTestUpdateObjectMetadataResponse1( state:EsuCallbackState ):void {
	    	checkState( state );
	    	var id:ObjectId = ObjectId( state.getResult() );
	        Assert.assertNotNull( "null ID returned", id );
	        cleanup.push( id );
	    	
	    	var stateObject:Object = state.getStateObject();
	    	var unlistable:Metadata = Metadata(stateObject["unlistable"]);
	    	var mlist:MetadataList = MetadataList( stateObject["mlist"] );
	        unlistable.setValue( "bar" );
	        stateObject["id"] = id;
	        
	        this.esu.setUserMetadata( id, mlist, 
	        	addAsync( handleTestUpdateObjectMetadataResponse2, TIMEOUT ), stateObject );	    	
	    }
	    private function handleTestUpdateObjectMetadataResponse2( state:EsuCallbackState ):void {
	    	checkState( state );
	    	var stateObject:Object = state.getStateObject();
	    	
	    	this.esu.getUserMetadata( stateObject["id"], null,
	    		addAsync( handleTestUpdateObjectMetadataResponse3, TIMEOUT ), stateObject );
	    }
	    private function handleTestUpdateObjectMetadataResponse3( state:EsuCallbackState ):void {
	    	checkState( state );
	    	var stateObject:Object = state.getStateObject();
	    	var meta:MetadataList = MetadataList( state.getResult() );

	        Assert.assertEquals( "value of 'unlistable' wrong", "bar", meta.getMetadata( "unlistable" ).getValue() );

	    	// Check that content was not modified
	        this.esu.readObject( stateObject["id"], null, null,
	        	addAsync( handleTestUpdateObjectMetadataResponse4, TIMEOUT ), stateObject );
	    }
	    private function handleTestUpdateObjectMetadataResponse4( state:EsuCallbackState ):void {
	    	checkState( state );
	    	
	    	var content:String = byteArrayToString( ByteArray(state.getResult()) );

	    	Assert.assertEquals( "object content wrong", "hello", content );
	    }
	    
	    
	    public function testUpdateObjectAcl():void {
	        // Create an object with an ACL
	        var acl:Acl = new Acl();
	        acl.addGrant( new Grant( new Grantee( UID, Grantee.GRANT_TYPE_USER ), Permission.FULL_CONTROL ) );
	        var other:Grant = new Grant( Grantee.OTHER, Permission.READ );
	        acl.addGrant( other );
	        var stateObject:Object = new Object();
	        stateObject["acl"] = acl;
	        stateObject["other"] = other;
	        
	        this.esu.createObject( acl, null, null, null,
	        	addAsync( handleTestUpdateObjectAclResponse1, TIMEOUT ), stateObject );
	    }
	    
	    private function handleTestUpdateObjectAclResponse1( state:EsuCallbackState ):void {
	    	checkState( state );
	    	var id:ObjectId = ObjectId( state.getResult() );
	        Assert.assertNotNull( "null ID returned", id );
	        cleanup.push( id );
	        
	        var stateObject:Object = state.getStateObject();
	        stateObject["id"] = id;
	        
	        // Read back the ACL and make sure it matches
	        this.esu.getAcl( id,
	        	addAsync( handleTestUpdateObjectAclResponse2, TIMEOUT ), stateObject );
	    }
	    private function handleTestUpdateObjectAclResponse2( state:EsuCallbackState ):void {
	    	checkState( state );
	        var stateObject:Object = state.getStateObject();
	        var newacl:Acl = Acl( state.getResult() );
	        var acl:Acl = Acl( stateObject["acl"] );
	        var id:ObjectId = ObjectId( stateObject["id"] );
	        var other:Grant = Grant( stateObject["other"] );
	        trace( "Comparing " + newacl + " with " + acl );
	        
	        Assert.assertTrue( "ACLs don't match", acl.equals( newacl ) );
	        
	        // Change the ACL and update the object.
	        acl.removeGrant( other );
	        var o2:Grant = new Grant( Grantee.OTHER, Permission.NONE );
	        acl.addGrant( o2 );
	        this.esu.setAcl( id, acl,
	        	addAsync( handleTestUpdateObjectAclResponse3, TIMEOUT ), stateObject );
	    }
	    private function handleTestUpdateObjectAclResponse3( state:EsuCallbackState ):void {
	    	checkState( state );
	        var stateObject:Object = state.getStateObject();
	        var id:ObjectId = ObjectId( stateObject["id"] );
	    	
	        // Read the ACL back and check it
	        this.esu.getAcl( id, addAsync( handleTestUpdateObjectAclResponse4, TIMEOUT ), stateObject );
	    }
	    private function handleTestUpdateObjectAclResponse4( state:EsuCallbackState ):void {
	    	checkState( state );
	        var stateObject:Object = state.getStateObject();
	        var newacl:Acl = Acl( state.getResult() );
	        var acl:Acl = Acl( stateObject["acl"] );

	        trace( "Comparing " + newacl + " with " + acl );
	        Assert.assertTrue( "ACLs don't match", acl.equals( newacl ) );
	    }
	    
	    
	
	    /**
	     * Tests updating an object's contents
	     */
	    public function testUpdateObjectContent():void {
	        // Create an object
	        this.esu.createObject( null, null, stringToByteArray( "hello" ), "text/plain",
	        	addAsync( handleTestUpdateObjectContentResponse1, TIMEOUT ), null );
	    }
	    
	    private function handleTestUpdateObjectContentResponse1( state:EsuCallbackState ):void {
	    	checkState( state );
	    	var id:ObjectId = ObjectId( state.getResult() );
	        Assert.assertNotNull( "null ID returned", id );
	        cleanup.push( id );
	        // Update part of the content
	        var extent:Extent = new Extent( 1,1 );
	        this.esu.updateObject( id, null, null, extent, stringToByteArray( "u" ), null,
	        	addAsync( handleTestUpdateObjectContentResponse2, TIMEOUT ), id ); 
	    }
	    private function handleTestUpdateObjectContentResponse2( state:EsuCallbackState ):void {
	    	checkState( state );
	    	var id:ObjectId = ObjectId( state.getStateObject() );

	        // Read back the content and check it
	        this.esu.readObject( id, null, null,
	        	addAsync( handleTestUpdateObjectContentResponse3, TIMEOUT ), null );
	    }
	    private function handleTestUpdateObjectContentResponse3( state:EsuCallbackState ):void {
	    	checkState( state );
	    	var content:String = byteArrayToString( ByteArray(state.getResult()) );

	        Assert.assertEquals( "object content wrong", "hullo", content );
	    }
	    
	
	    /**
	     * Test replacing an object's entire contents
	     */
	    public function testReplaceObjectContent():void {
	        // Create an object
	        this.esu.createObject( null, null, stringToByteArray( "hello" ), "text/plain",
	        	addAsync( handleTestReplaceObjectContentResponse1, TIMEOUT ), null );
	    }
	    private function handleTestReplaceObjectContentResponse1( state:EsuCallbackState ):void {
	    	checkState( state );
	    	var id:ObjectId = ObjectId( state.getResult() );
	        Assert.assertNotNull( "null ID returned", id );
	        cleanup.push( id );

	        // Update all of the content
	        this.esu.updateObject( id, null, null, null, stringToByteArray( "bonjour" ), null,
	        	addAsync( handleTestReplaceObjectContentResponse2, TIMEOUT ), id ); 
	    }
	    private function handleTestReplaceObjectContentResponse2( state:EsuCallbackState ):void {
	    	checkState( state );
	    	var id:ObjectId = ObjectId( state.getStateObject() );

	        // Read back the content and check it
	        this.esu.readObject( id, null, null,
	        	addAsync( handleTestReplaceObjectContentResponse3, TIMEOUT ), null );
	    }
	    private function handleTestReplaceObjectContentResponse3( state:EsuCallbackState ):void {
	    	checkState( state );
	    	var content:String = byteArrayToString( ByteArray(state.getResult()) );

	        Assert.assertEquals( "object content wrong", "bonjour", content );
	    }

	    /**
	     * Test the UploadHelper's create method
	     */
	    public function testCreateHelper():void {
	        // use a blocksize of 1 to test multiple transfers.
	        var uploadHelper:UploadHelper = new UploadHelper( this.esu, 1 );
	        var ba:ByteArray = stringToByteArray( "hello" );
	        ba.position = 0;
	        var callback:Function = addAsync( handleTestCreateHelperComplete1, TIMEOUT );
	        uploadHelper.addEventListener( Event.COMPLETE, callback );
	        uploadHelper.addEventListener( ErrorEvent.ERROR, callback );

	
	        // Create an object from our file stream
	        uploadHelper.createObject( ba, ba.length, null, null );

	    }
	    
	    private function handleTestCreateHelperComplete1( event:Event ):void {
	    	if( event is ErrorEvent ) {
		    	// Fail the test
		    	Assert.fail( "Upload failed: " + ErrorEvent(event).text );	    
		    	return;
	    	}
	    	var uploadHelper:UploadHelper = UploadHelper( event.target );
	    	
	    	Assert.assertFalse( "Upload should have succeeded", uploadHelper.failed );
	    	Assert.assertTrue( "Upload should be complete", uploadHelper.complete );
	        Assert.assertNotNull( "null ID returned", uploadHelper.objectId );
	        
	        cleanup.push( uploadHelper.objectId );
	    	
	        // Read contents back and check them
			this.esu.readObject( uploadHelper.objectId, null, null, addAsync( 
				handleTestCreateHelperComplete2, TIMEOUT ), null );
	    }
	    
	    private function handleTestCreateHelperComplete2( state:EsuCallbackState ):void {
	    	checkState( state );
	    	
	    	var content:String = byteArrayToString( ByteArray(state.getResult()) );

	        Assert.assertEquals( "object content wrong", "hello", content );
	    }
	    
	    
	
	    /**
	     * Test the UploadHelper's update method
	     */
	    public function testUpdateHelper():void {
	        // Create an object with content.
	        this.esu.createObject( null, null, stringToByteArray( "Four score and twenty years ago" ), 
	        	"text/plain", addAsync( handleTestUpdateHelperResponse1, TIMEOUT ), null );
	    }
	    
	    
	    private function handleTestUpdateHelperResponse1( state:EsuCallbackState ):void {
	    	checkState( state );
	    	var id:ObjectId = ObjectId( state.getResult() );
	        Assert.assertNotNull( "null ID returned", id );
	        cleanup.push( id );
	    	

	        // use a blocksize of 1 to test multiple transfers.
	        var uploadHelper:UploadHelper = new UploadHelper( this.esu, 1 );
	        var callback:Function = addAsync( handleTestUpdateHelperResponse2, TIMEOUT );
	        uploadHelper.addEventListener( Event.COMPLETE, callback );
	        uploadHelper.addEventListener( ErrorEvent.ERROR, callback );
		
	        // update the object contents
	        var ba:ByteArray = stringToByteArray( "hello" );
	        ba.position = 0;
	
	        uploadHelper.updateObject( id, ba, ba.length, null, null );
	    }
	    
	    private function handleTestUpdateHelperResponse2( event:Event ):void {
	    	if( event is ErrorEvent ) {
		    	// Fail the test
		    	Assert.fail( "Upload failed: " + ErrorEvent(event).text );	    
		    	return;
	    	}
	    	var uploadHelper:UploadHelper = UploadHelper( event.target );
	    	
	    	Assert.assertFalse( "Upload should have succeeded", uploadHelper.failed );
	    	Assert.assertTrue( "Upload should be complete", uploadHelper.complete );
	        Assert.assertNotNull( "null ID returned", uploadHelper.objectId );

	    	
	    	this.esu.readObject( uploadHelper.objectId, null, null, 
	    		addAsync( handleTestUpdateHelperResponse3, TIMEOUT ), null );
	    }
	    
	    private function handleTestUpdateHelperResponse3( state:EsuCallbackState ):void {
	    	checkState( state );
	
	        // Read contents back and check them
	    	var content:String = byteArrayToString( ByteArray(state.getResult()) );
	    	
	        Assert.assertEquals( "object content wrong", "hello", content );            
	    }
	    
	    
	
//	    /**
//	     * Tests the download helper.  Tests both single and multiple requests.
//	     */
//	    public function testDownloadHelperWithChecksum():void {
//	    	var data:ByteArray = new ByteArray();
//	    	var length:int = 5*1024*1024;
//	    	trace( "Generating data" );
//	    	data.length = length;
//	    	for( var i:int = 0; i<length; i++ ) {
//	    		data[i] = i%256;
//	    	}
//	    	trace( "Creating object" );
//	    	var stateObj:Object = new Object();
//	    	stateObj.data = data;
//	        var mlist:MetadataList = new MetadataList();
//	        var policy:Metadata = new Metadata( "policy", "erasure", false );
//	        mlist.addMetadata( policy );
//	        var checksum:Checksum = new Checksum( Checksum.ALGORITHM_SHA0 );
//	        // Create an object with content.
//	        this.esu.createObject( null, mlist, 
//	        	data, "text/plain",
//	        	addAsync( handleTestDownloadHelperResponse1, TIMEOUT*10 ), stateObj, checksum );
//	    }
	    
	    private function handleTestDownloadHelperResponse1( state:EsuCallbackState ):void {
	    	checkState( state );
	    	var id:ObjectId = ObjectId( state.getResult() );
	        Assert.assertNotNull( "null ID returned", id );
	        cleanup.push( id );
	        var stateObj:Object = state.getStateObject();
	
	        // Download the content
	        var downloadHelper:DownloadHelper = new DownloadHelper( this.esu );
	        downloadHelper.checksumming = true;
	        var ba:ByteArray = new ByteArray();
	        
	        // register listeners
	        var callback:Function = addAsync( handleTestDownloadHelperResponse2, TIMEOUT*10, ba );
	        downloadHelper.addEventListener( Event.COMPLETE, callback );
	        downloadHelper.addEventListener( ErrorEvent.ERROR, callback );
	        downloadHelper.userState = stateObj;
	        
	        downloadHelper.readObject( id, ba );	        
	    }
	    
	    private function handleTestDownloadHelperResponse2( event:Event, ba:ByteArray ):void {
	    	if( event is ErrorEvent ) {
		    	// Fail the test
		    	Assert.fail( "Upload failed: " + ErrorEvent(event).text );	    
		    	return;
	    	}
	        // Check the download
	        var downloadHelper:DownloadHelper = DownloadHelper( event.target );
	        var id:Identifier = downloadHelper.objectId;
	        var stateObj:Object = downloadHelper.userState;
	        var originalData:ByteArray = ByteArray(stateObj.data);
	        
	        ba.position = 0;
	    	Assert.assertEquals( "Arrays should be same length", originalData.length, ba.length );
	    	
	    	for( var i:int=0; i<originalData.length; i++ ) {
	    		Assert.assertEquals( "Arrays differ at offset " + i, originalData[i], ba[i] );	
	    	}
	    	
	    	// Check checksum
	    	var ck:Checksum = downloadHelper.checksum;
	    	if( ck.getExpectedValue() != null ) {
	    		Assert.assertEquals( "Checksum failed on download", ck.getExpectedValue(), ck.toString() );
	    	}

	    }
	    
	    /**
	     * Test reading and writing a large object to test reading chunked
	     * responses.
	     */
//	    public function testLargeObject():void {
//	    	// Generate 10MB of data
//	    	var data:ByteArray = new ByteArray();
//	    	var length:int = 10*1024*1024;
//	    	trace( "Generating data" );
//	    	data.length = length;
//	    	for( var i:int = 0; i<length; i++ ) {
//	    		data[i] = i%256;
//	    	}
//	    	trace( "Creating object" );
//	    	
//	    	var uh:UploadHelper = new UploadHelper( this.esu );
//	    	var callback:Function = addAsync( handleTestLargeObjectResponse1, TIMEOUT*10, data );
//	    	uh.addEventListener( Event.COMPLETE, callback );
//	    	uh.addEventListener( ErrorEvent.ERROR, callback );
//	    	uh.createObject( data, length, null, null );
//	    }
	    
	    private function handleTestLargeObjectResponse1( event:Event, ba:ByteArray ):void {
	    	if( event is ErrorEvent ) {
		    	// Fail the test
		    	Assert.fail( "Upload failed: " + ErrorEvent(event).text );	    
		    	return;
	    	}
	    	var uploadHelper:UploadHelper = UploadHelper( event.target );
	    	
	    	Assert.assertFalse( "Upload should have succeeded", uploadHelper.failed );
	    	Assert.assertTrue( "Upload should be complete", uploadHelper.complete );
	        Assert.assertNotNull( "null ID returned", uploadHelper.objectId );
	        
	        cleanup.push( uploadHelper.objectId );
	        
	        var dlData:ByteArray = new ByteArray();
	        dlData.length = ba.length;
	        var state:Object = new Object();
	        state["origData"] = ba;
	        state["dlData"] = dlData; 
	        
	        // Read contents back
			var downloadHelper:DownloadHelper = new DownloadHelper( this.esu, 512*1024 );
			var callback:Function = addAsync( handleTestLargeObjectResponse2, TIMEOUT*10, state );
			downloadHelper.addEventListener( Event.COMPLETE, callback );
			downloadHelper.addEventListener( ErrorEvent.ERROR, callback );
			downloadHelper.readObject( uploadHelper.objectId, dlData );
	    }
	    
	    private function handleTestLargeObjectResponse2( event:Event, state:Object ):void {
	    	if( event is ErrorEvent ) {
		    	// Fail the test
		    	Assert.fail( "Upload failed: " + ErrorEvent(event).text );	    
		    	return;
	    	}
	    	// Check response data
	    	trace( "Verifying arrays" );
	    	var origData:ByteArray = state["origData"];
	    	var dlData:ByteArray = state["dlData"];
	    	
	    	Assert.assertEquals( "Arrays should be same length", origData.length, dlData.length );
	    	
	    	for( var i:int=0; i<origData.length; i++ ) {
	    		Assert.assertEquals( "Arrays differ at offset " + i, origData[i], dlData[i] );
	    		
	    	}
	    }
	    
		public function testListDirectory():void {
			
			var dir:String = rand8char();
			var file:String = rand8char() + ".txt";
			var dir2:String = rand8char();
	        var dirPath:ObjectPath = new ObjectPath( "/" + dir + "/" );
	    	var op:ObjectPath = new ObjectPath( "/" + dir + "/" + file );
	    	var dirPath2:ObjectPath = new ObjectPath( "/" + dir + "/" + dir2 + "/" );
	    	
	    	var state:Object = new Object();
	    	state.dirPath = dirPath;
	    	state.op = op;
	    	state.dirPath2 = dirPath2;
	    	
	    	this.esu.createObjectOnPath( dirPath, null, null, null, null, addAsync( handleTestListDirectory1, TIMEOUT ), state );
	 	}
	    	
	    public function handleTestListDirectory1( state:EsuCallbackState ):void {
	    	checkState( state );
	    	
	    	var os:Object = state.getStateObject();
	    	Assert.assertNotNull( state.getResult() );
	    	
	        this.esu.createObjectOnPath( ObjectPath(os.op), null, null, null, null, addAsync( handleTestListDirectory2, TIMEOUT ), os );
	    }
	    
	    public function handleTestListDirectory2( state:EsuCallbackState ):void {
	    	checkState( state );
	    	
	    	var os:Object = state.getStateObject();
	    	Assert.assertNotNull( state.getResult() );
	    	os.id = state.getResult();
	        
	        this.esu.createObjectOnPath( ObjectPath(os.dirPath2), null, null, null, null, addAsync( handleTestListDirectory3, TIMEOUT ), os );
	    }
	    
	    public function handleTestListDirectory3( state:EsuCallbackState ):void {
	    	checkState( state );
	    	
	    	var os:Object = state.getStateObject();
	    	Assert.assertNotNull( state.getResult() );

	        cleanup.push( Identifier(os.op) );
	        cleanup.push( Identifier(os.dirPath2) );
	        cleanup.push( Identifier(os.dirPath) );
	        trace( "Path: " + os.op + " ID: " + os.id );
	
	        // List the parent path
	        esu.listDirectory( ObjectPath(os.dirPath), null, addAsync( handleTestListDirectory4, TIMEOUT ), os );
	    }
	    
	    public function handleTestListDirectory4( state:EsuCallbackState ):void {
	    	checkState( state );
	    	
	    	var os:Object = state.getStateObject();
	    	Assert.assertNotNull( state.getResult() );
	    	
	    	var content:Array = state.getResult() as Array;
	    	
	        trace( "Dir content: " + content );
	        Assert.assertTrue( "File not found in directory", findInDirectory( ObjectPath( os.op ), content ) != -1 );
	        Assert.assertTrue( "subdirectory not found in directory", findInDirectory( ObjectPath( os.dirPath2 ), content ) != -1 );
		}
		
		private function findInDirectory( path:ObjectPath, arr:Array ):int {
	    	for( var i:int=0; i<arr.length; i++ ) {
	    		var de:DirectoryEntry = DirectoryEntry( arr[i] );
	    		if( path.equals( de.getPath() ) ) {
	    			return i;
	    		}
	    	}
	    	
	    	
	    	return -1;
	    }

		public function testRename():void {
			
			var file:String = rand8char() + ".txt";
			var file2:String = rand8char() + ".txt";
	    	
	    	var state:Object = new Object();
	    	state.file = file;
	    	state.file2 = file2;
	    	
	    	this.esu.createObjectOnPath( new ObjectPath(file), null, null, stringToByteArray("four score and seven years ago"), null, addAsync( handleTestRename1, TIMEOUT ), state );
	 	}
	    	
	    public function handleTestRename1( state:EsuCallbackState ):void {
	    	checkState( state );
	    	
	    	var os:Object = state.getStateObject();
	    	Assert.assertNotNull( state.getResult() );
	    	
	        this.esu.rename( new ObjectPath(os.file), new ObjectPath(os.file2), false, addAsync( handleTestRename2, TIMEOUT ), os );
	        
	    }
	    
	    private function handleTestRename2( state:EsuCallbackState ):void {
	    	checkState( state );
	    	
	    	var os:Object = state.getStateObject();
	    	os.id = state.getResult();
	        
	        this.esu.readObject( new ObjectPath(os.file2), null, null, addAsync( handleTestRename3, TIMEOUT ), os );
	    }
	    
	    private function handleTestRename3( state:EsuCallbackState ):void {
	    	checkState( state );
	    	var bytes:ByteArray = ByteArray( state.getResult() );
	    	var content:String = bytes.readUTFBytes( bytes.length );
	        
	    	Assert.assertEquals( "object content wrong", "four score and seven years ago", content );
	    }
	    
		
		/**
		 * This method tests various legal and illegal pathnames
		 * @throws Exception
		 */
		public function testPathNaming():void {
			var path:ObjectPath = new ObjectPath( "/some/file" );
			Assert.assertFalse( "File should not be directory", path.isDirectory() );
			path = new ObjectPath( "/some/file.txt" );
			Assert.assertFalse( "File should not be directory", path.isDirectory() );
			var path2:ObjectPath = new ObjectPath( "/some/file.txt" );
			//Assert.assertEquals( "Equal paths should be equal", path, path2 );
			
			path = new ObjectPath( "/some/file/with/long.path/extra.stuff.here.zip" );
			Assert.assertFalse( "File should not be directory", path.isDirectory() );
			
			path = new ObjectPath( "/" );
			Assert.assertTrue( "Directory should be directory", path.isDirectory() );
			
			path = new ObjectPath( "/long/path/with/lots/of/elements/" );
			Assert.assertTrue( "Directory should be directory", path.isDirectory() );
			
		}
		
		/**
		 * Tests the 'get all metadata' call using a path
		 * @param uid
		 * @throws Exception
		 */
		public function testGetAllMetadataByPath():void {
	    	var op:ObjectPath = new ObjectPath( "/" + rand8char() + ".tmp" );
	        // Create an object with an ACL
	        var acl:Acl = new Acl();
	        acl.addGrant( new Grant( new Grantee( UID, Grantee.GRANT_TYPE_USER ), Permission.FULL_CONTROL ) );
	        acl.addGrant( new Grant( Grantee.OTHER, Permission.READ ) );
	        var mlist:MetadataList = new MetadataList();
	        var listable:Metadata = new Metadata( "listable", "foo", true );
	        var unlistable:Metadata = new Metadata( "unlistable", "bar", false );
	        var listable2:Metadata = new Metadata( "listable2", "foo2 foo2", true );
	        var unlistable2:Metadata = new Metadata( "unlistable2", "bar2 bar2", false );
	        mlist.addMetadata( listable );
	        mlist.addMetadata( unlistable );
	        mlist.addMetadata( listable2 );
	        mlist.addMetadata( unlistable2 );
	        
	        var os:Object = new Object;
	        os.op = op;
	
	        this.esu.createObjectOnPath( ObjectPath( os.op ), acl, mlist, null, null, addAsync( handleGetAllMetadata1, TIMEOUT ), os);
	 	}
	 	
	 	public function handleGetAllMetadata1( state:EsuCallbackState ):void {
	 		checkState( state );
	 		var os:Object = state.getStateObject();
	 		var id:ObjectId = ObjectId(state.getResult());
	        Assert.assertNotNull( "null ID returned", id );
	        cleanup.push( Identifier(os.op) );
	        
	        // Read it back with HEAD call
	        this.esu.getAllMetadata( Identifier( os.op ), addAsync( handleGetAllMetadata2, TIMEOUT ), os );
	  	}
	  	
	  	public function handleGetAllMetadata2( state:EsuCallbackState ):void {
	  		checkState( state );
	  		
	  		var om:ObjectMetadata = ObjectMetadata( state.getResult() );
	        Assert.assertNotNull( "value of 'listable' missing", om.getMetadata().getMetadata( "listable" ) );
	        Assert.assertNotNull( "value of 'unlistable' missing", om.getMetadata().getMetadata( "unlistable" ) );
	        Assert.assertNotNull( "value of 'atime' missing", om.getMetadata().getMetadata( "atime" ) );
	        Assert.assertNotNull( "value of 'ctime' missing", om.getMetadata().getMetadata( "ctime" ) );
	        Assert.assertEquals( "value of 'listable' wrong", "foo", om.getMetadata().getMetadata( "listable" ).getValue() );
	        Assert.assertEquals( "value of 'unlistable' wrong", "bar", om.getMetadata().getMetadata( "unlistable" ).getValue() );
	
	        // Check the ACL
	        // not checking this by path because an extra groupid is added 
	        // during the create calls by path.
	        //Assert.assertEquals( "ACLs don't match", acl, om.getAcl() );
	
		}
		
			
		public function testShareablePathUrl():void {
			var stateObj:Object = new Object();
			stateObj.path = new ObjectPath( "/" + rand8char() );
			stateObj.text = "Four score and seven years ago...";
			this.esu.createObjectOnPath( ObjectPath( stateObj.path ), 
				null, null, stringToByteArray( String(stateObj.text) ), 
				"text/plain", addAsync( handleTestShareablePathUrl1, TIMEOUT ), 
				stateObj );
		}
		
		private function handleTestShareablePathUrl1( state:EsuCallbackState ):void {
			checkState( state );
			var id:ObjectId = ObjectId( state.getResult() );
			var stateObj:Object = state.getStateObject();
			
			Assert.assertNotNull( "null ID returned", id );
			cleanup.push( ObjectPath( stateObj.path ) );
			
			// Get the URL
			var expires:Date = new Date();
			expires.time += 3600000; // 1hr
			stateObj.url = esu.getShareableUrl( ObjectPath( stateObj.path ), expires );
			trace( "Share URL: " + stateObj.url );
			
			var loader:URLLoader = new URLLoader();
			var callback:Function = addAsync( handleTestShareableUrl2, TIMEOUT, stateObj );
			stateObj.loader = loader;
			loader.addEventListener( Event.COMPLETE, callback );
			loader.addEventListener( IOErrorEvent.IO_ERROR, callback );		
			loader.dataFormat = URLLoaderDataFormat.TEXT;
			loader.load( new URLRequest( String(stateObj.url) ) );
		}

		
		public function testShareableUrl():void {
			var stateObj:Object = new Object();
			stateObj.text = "Four score and seven years ago...";
	        this.esu.createObject( null, null, stringToByteArray( stateObj.text ), "text/plain", addAsync( handleTestShareableUrl1, TIMEOUT ), stateObj );
		}
		
		private function handleTestShareableUrl1( state:EsuCallbackState ):void {
			checkState( state );
			var id:ObjectId = ObjectId( state.getResult() );
			
			Assert.assertNotNull( "null ID returned", id );
			cleanup.push( id );
			
			// Get the URL
			var stateObj:Object = state.getStateObject();
			var expires:Date = new Date();
			expires.time += 3600000; // 1hr
			stateObj.url = esu.getShareableUrl( id, expires );
			trace( "Share URL: " + stateObj.url );
			
			var loader:URLLoader = new URLLoader();
			var callback:Function = addAsync( handleTestShareableUrl2, TIMEOUT, stateObj );
			stateObj.loader = loader;
			loader.addEventListener( Event.COMPLETE, callback );
			loader.addEventListener( IOErrorEvent.IO_ERROR, callback );		
			loader.dataFormat = URLLoaderDataFormat.TEXT;
			loader.load( new URLRequest( String(stateObj.url) ) );
		}
		
		private function handleTestShareableUrl2( event:Event, stateObj:Object ):void {
			if( event is IOErrorEvent ) {
				fail( "Failed to load URL: " + IOErrorEvent( event ).text );
				return;
			}
			
			var loader:URLLoader = URLLoader( stateObj.loader );
			var text:String = String(loader.data);
			
			Assert.assertEquals( "Downloaded data doesn't match", stateObj.text, text );
		}

	    
	    
	    private function stringToByteArray( str:String ):ByteArray {
	    	var ba:ByteArray = new ByteArray();
	    	ba.writeUTFBytes( str );
	    	return ba;
	    }
	    
	    private function byteArrayToString( ba:ByteArray ):String {
	    	return ba.readUTFBytes( ba.length );
	    }
	    
	    private function checkState( state:EsuCallbackState ):void {
	    	if( !state.wasSuccessful() ) {
	    		throw state.getError();
	    	}
	    }
	    
	    private static const CHARS:String = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-+=_";
	    
	    private function rand8char():String {
	    	var str:String = "";
	    	for( var i:int=0; i<8; i++ ) {
	    		var index:int = rand.nextByte() % CHARS.length;
	    		str += CHARS.charAt( index );
	    	}
	    	
	    	return str;
	    }
		
	}
}